#region Apache License
//
// Licensed to the Apache Software Foundation (ASF) under one or more 
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership. 
// The ASF licenses this file to you under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with 
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#endregion

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using IPSim.Data;
using IPSim.Util;

namespace IPSim.Core
{
    class RIPInterface
    {
        private const ushort RIPPort = 520;
        /// <summary>
        /// Buffer pf RIPPackets received from UDP Interface
        /// </summary>
        private Queue<RIPPacket> m_ripBuffer;

        private Timer m_announceTimer;

        private Dictionary<IPAddress, RIPTableEntry> m_routeTable;

        /// <summary>
        /// Thread collecting packets from buffer and calling process method for them.
        /// Consists of PacketProcessorMethod()
        /// </summary>
        private Thread m_packetProcessorThread;

        /// <summary>
        /// Underlying UDP Interface
        /// </summary>
        private UDPInterface m_udpInterface1;
        private UDPInterface m_udpInterface2;
        private UDPInterface m_udpInterface3;
        private UDPInterface m_udpInterface4;


        public RIPInterface(UDPInterface udpInterface1, UDPInterface udpInterface2, UDPInterface udpInterface3, UDPInterface udpInterface4)
        {
            Random r = new Random();
            m_udpInterface1 = udpInterface1;
            m_udpInterface2 = udpInterface2;
            m_udpInterface3 = udpInterface3;
            m_udpInterface4 = udpInterface4;
            m_ripBuffer = new Queue<RIPPacket>();
            m_routeTable = new Dictionary<IPAddress, RIPTableEntry>();
            m_udpInterface1.TransportAppData += new TransportAppDataEventHandler(ReceiveAppData);
            m_udpInterface2.TransportAppData += new TransportAppDataEventHandler(ReceiveAppData);
            m_udpInterface3.TransportAppData += new TransportAppDataEventHandler(ReceiveAppData);
            m_udpInterface4.TransportAppData += new TransportAppDataEventHandler(ReceiveAppData);
            m_announceTimer = new Timer(new TimerCallback(AnnounceRoutingTable), null, 1000, 4000 + r.Next(-2000,2000));
            m_packetProcessorThread = new Thread(new ThreadStart(PacketProcessorMethod));
            m_packetProcessorThread.Start();            
            Logger.log("RIP Interface CREATED!", LogLevel.INFO);            
        }


        /// <summary>
        /// Thread method collecting packets from buffer and calling process method for them.
        /// </summary>
        private void PacketProcessorMethod()
        {
            RIPPacket tempPacket;
            while (true)
            {
                lock (m_ripBuffer)
                {
                    tempPacket = null;
                    while (m_ripBuffer.Count == 0) Monitor.Wait(m_ripBuffer);
                    tempPacket = m_ripBuffer.Dequeue();
                }
                ProcessPacket(tempPacket);
            }
        }

        private void AnnounceRoutingTable(object state)
        {
            RIPPacket tempPacket = new RIPPacket(RIPCommand.Response);
            foreach(IPAddress ipAddr in m_routeTable.Keys)
                tempPacket.NetInfoList.Add(new RIPNetInfo(ipAddr, m_routeTable[ipAddr].Metric));
            m_udpInterface1.SendUDPPacket(new UDPPacket(RIPPort, RIPPort, tempPacket), IPAddress.BroadcastAddress);
            m_udpInterface2.SendUDPPacket(new UDPPacket(RIPPort, RIPPort, tempPacket), IPAddress.BroadcastAddress);
            m_udpInterface3.SendUDPPacket(new UDPPacket(RIPPort, RIPPort, tempPacket), IPAddress.BroadcastAddress);
            m_udpInterface4.SendUDPPacket(new UDPPacket(RIPPort, RIPPort, tempPacket), IPAddress.BroadcastAddress);
        }

        private void ReceiveAppData(object source, TransportAppDataEventArgs e)
        {
            RIPPacket tempPacket;
            if (e.AppData is RIPPacket)
            {
                tempPacket = e.AppData as RIPPacket;
                lock (m_ripBuffer)
                {
                    m_ripBuffer.Enqueue(tempPacket);
                    Monitor.PulseAll(m_ripBuffer);
                }
            }
        }

        /// <summary>
        /// Method that processes received RIP packets
        /// </summary>
        /// <param name="udpPacket"></param>
        private void ProcessPacket(RIPPacket ripPacket)
        {
            Logger.log("RIP packet received", LogLevel.INFO);
            UpdateRoutingTable(ripPacket.SenderIPAddress, ripPacket.SenderIPAddress, 0);
            if (ripPacket.Command == RIPCommand.Request)
                AnnounceRoutingTable(null);
            else
            {
                foreach (RIPNetInfo netInfo in ripPacket.NetInfoList)
                {
                    UpdateRoutingTable(netInfo.IPAddress, ripPacket.SenderIPAddress, netInfo.Metric);
                }
            }
        }

        private void UpdateRoutingTable(IPAddress ipAddress, IPAddress nextHop, byte metric)
        {
            if(m_routeTable.ContainsKey(ipAddress))
            {
                if (m_routeTable[ipAddress].Metric > metric)
                {
                    m_routeTable[ipAddress] = new RIPTableEntry(nextHop, Convert.ToByte(metric + 1));
                    Logger.log("RIP - IPAddress:" + ipAddress + "  with next hop:" + nextHop + " and metric:" + metric);
                }
            }
            else
            {
                m_routeTable[ipAddress] = new RIPTableEntry(nextHop, Convert.ToByte(metric+1));
                Logger.log("RIP - IPAddress:" + ipAddress + "  with next hop:" + nextHop + " and metric:" + metric);
            }
        }

        ~RIPInterface()
        {
            Logger.log("RIP Interface stoping", LogLevel.INFO);
            if (m_packetProcessorThread != null)
            {
                m_packetProcessorThread.Abort();
            }
            if (m_announceTimer != null)
            {
                m_announceTimer.Change(-1,-1);
                m_announceTimer.Dispose();
            }
        }
    }
}

